#!/usr/bin/env python
from math import floor
from threading import Timer
from time import time

from kivy.logger import Logger
from kivy.graphics.vertex_instructions import Rectangle
from kivy.graphics import Canvas, Color, Callback
from kivy.metrics import dp, inch
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.scrollview import ScrollView
from kivy.uix.textinput import TextInput
from kivy.uix.widget import Widget

from dryfire.view.messageView import MessageView
from dryfire.viewModel.calibrationsViewModel import CalibrationsViewModel

__all__ = ('CalibrationsView')

class _CalibratedSquare(Widget):

    def __init__(self, pixels, **kwargs):
        """pixels is the number of pixels for the width.
        """
        Widget.__init__(self, **kwargs)
        self.canvas = Canvas()
        self._rectangle = _CalibratedRectangle(self, pixels)
        self.bind(pos=self._rectangle.draw)

    def handle_on_open(self, instance):
        self._rectangle.draw()
        
    def handle_on_larger_pressed(self, instance):
        self._rectangle.larger()

    def handle_on_smaller_pressed(self, instance):
        self._rectangle.smaller()

    @property
    def value(self):
        # this a calibrated version of kivy.metrix.inch(1)
        # it is the number of pixels in one calibrated inch for this monitor.
        w, h = self._rectangle.size
        return w
        

class _CalibratedRectangle(Rectangle):

    default_color_on = Color(1.0, 1.0, 1.0, 1.0)

    def __init__(self, view, pixels):
        Rectangle.__init__(self, size=(pixels, inch(1)))
        self._max_width = inch(2)
        self._view = view
        self._drawn = False
        self._draw_timer = None
        self._last_larger = time()
        self._last_smaller = time()
    
    def smaller(self):
        w, h = self.size
        if w > 4:
            now = time()
            if now - self._last_smaller <= 0.250:
                # less than a 1/4 second
                # speed up
                speed = 5
            else:
                # normal
                speed = 1
            w -= speed
            if w < 4:
                w = 4
            self._last_smaller = now
            self.size = (w, h)
            self.draw()

    def larger(self):
        w, h = self.size
        if w < self._max_width:
            now = time()
            if now - self._last_larger <= 0.250:
                # less than a 1/4 second
                # speed up
                speed = 5
            else:
                # normal
                speed = 1
            w += speed
            if w > self._max_width:
                w = self._max_width
            self._last_larger = now
            self.size = (w, h)
            self.draw()
        
    def draw(self, *not_used):
        w, h = self.size
        vw, vh = self._view.size
        # use the empty space to center and middle the target.
        y = int(floor((vh - h) / 2))
        x = int(floor((vw - self._max_width) / 2))
        vx, vy = self._view.pos
        self.pos = (x+vx, y+vy)
        # add the rectangle to the canvas
        canvas = self._view.canvas
        if self._drawn is True:
            canvas.clear()
        else:
            self._drawn = True
        canvas.add(_CalibratedRectangle.default_color_on)
        canvas.add(self)
        canvas.ask_update()
        
        
class _CalibrationsRecordButton(Button, MessageView):
    def __init__(self, ident, description, vm_calibrations_record_handler, **kargs):
        self.ident = ident
        if 'text' in kargs:
            del(kargs['text'])
        kargs['markup'] = True   
        kargs['on_press'] = self.w_handle_calibrations_record_button_on_press
        kargs['size_hint'] = (1.0, None)
        kargs['height'] = dp(50)
        Button.__init__(self, **kargs)
        MessageView.__init__(self)
        self.description = None
        self.reset_text(description)
        self.vm_calibrations_record_handler = vm_calibrations_record_handler
        
    def reset_text(self, description):
        desc = description.strip()
        if desc != self.description:
            self.description = desc
            self.text = '[b]' + desc + '[/b]'
        
    def w_handle_calibrations_record_button_on_press(self, instance):
        self.vm_calibrations_record_handler(self.ident)
            

class _CalibrationsListView(ScrollView):
    def __init__(self, vm_calibrations_record_handler, **kargs):
        kargs['do_scroll_x'] = False
        ScrollView.__init__(self, **kargs)
        # view model command handling method
        self.vm_calibrations_record_handler = vm_calibrations_record_handler
        # create a default grid layout with custom width/height
        self._layout = GridLayout(cols=1,
                                  size_hint=(1.0, None))
        # when we add children to the grid layout, its size doesn't change at
        # all. we need to ensure that the height will be the minimum required to
        # contain all the childs. (otherwise, we'll child outside the bounding
        # box of the childs)
        self._layout.bind(minimum_height=self._layout.setter('height'))
        self.add_widget(self._layout)
        
    def log(self, ident, description):
        w = _CalibrationsRecordButton(ident, description, self.vm_calibrations_record_handler)
        y = self.scroll_y
        self._layout.add_widget(w)
        # if the scroll view was scrolled to the end
        # then stay at the end
        if y == 1.0:
            self.scroll_y = y
        return w


class CalibrationsView(FloatLayout, MessageView):
    """a view for the domains page
        
        use bind_apply to bind the vm method to read and store the vars
        use bind_reset to bind the vm method to reset the vars

    """
    submit_command = 'Submit'
    create_command = 'Create A New Calibration.'
    delete_command = 'Delete'
    edit_command = 'Edit'
    
    commands = (edit_command, delete_command)
    
    page_intro = "This page is where you can calibrate this application"\
                 " for your monitor or monitors."\
                 "\nYou will need a ruler to measure the width of a square on"\
                 " your screen as you adjust the width of the square to"\
                 " exactly one inch."\
                 "\nYou can edit or delete one of your calibrations by"\
                 " clicking on its name in the list."

    def __init__(self, **kargs):
        FloatLayout.__init__(self, **kargs)
        MessageView.__init__(self)
        # page heading
        text = CalibrationsView.page_intro.format(CalibrationsView.create_command) 
        self.intro = Label(text=text,
                           size_hint=(None, 0.4),
                           pos_hint={'center_x':0.5, 'y':0.6},
                           line_height=1.5)
        self.bind(width=self.on_width_handler)
        self.add_widget(self.intro)
        # initialize the view model
        view_model = CalibrationsViewModel(self)
        # 'create new record' button
        self.create_button = Button(text=self.__class__.create_command,
                                 size_hint=(1.0, 0.2),
                                 pos_hint={'x':0.0, 'y':0.4})
        self.create_button.bind(on_press=view_model.w_handle_create_button_on_press)
        self.add_widget(self.create_button)
        # list of current domains
        self._list_view = _CalibrationsListView(view_model.w_handle_calibrations_record,
                                        size_hint=(1.0, 0.4),
                                        pos_hint={'x':0.0, 'y':0.0})
        self.add_widget(self._list_view)
        self._calibrations = {}
        view_model.v_initialize_view()

    def on_width_handler(self, instance, value):
        if value > 40:
            value -= 20
        self.intro.text_size = (value-20, None)
 
    # vm methods

    def vm_add_calibration(self, ident, description):
        if ident not in self._calibrations:
            self._calibrations[ident] = self._list_view.log(ident, description)

    def vm_show_calibration_added(self, ident, description):
        # inform user of the added calibrations
        self.vm_add_calibration(ident, description)
        self.message_success('Added "%s"' % (description))
        
    def vm_show_calibration_edited(self, ident, description):
        if ident in self._calibrations:
            self._calibrations[ident].reset_text(description)
            # inform user of the edit
            self.message_success('Modified "%s"' % (description))

    def vm_show_calibration_deleted(self, ident):
        if ident in self._calibrations:
            description = self._calibrations[ident].description 
            self._calibrations[ident].parent.remove_widget(self._calibrations[ident])
            del(self._calibrations[ident])
            # inform user of the deleted calibrations
            self.message_success('Deleted "%s"' % (description))

    # popup

    def select_calibration(self, title, value, command_handler, *command_handler_largs, **kwargs):
        """
        kwargs options:
        help='text':
            * the text displayed below the title
        back_handler=my_back_callback:
            * is the optional back button callback
            * the callback will only be sent params *command_handler_largs
            * if back_handler is provided then a back button will be displayed
        """
        self._messageview_command_handler = command_handler
        self._messageview_command_handler_largs = command_handler_largs
        self._process_kwargs(**kwargs)
        help = kwargs.get('help', '')
        # title
        spaces = 0
        parts = 1
        height = 0
        # help
        using_help = len(help) > 0
        if using_help:
            intro = Label(text=help,
                          text_size=(dp(self._text_width), None),
                          size_hint=(None, None))
            intro.texture_update()
            intro.size = intro.texture_size
            height += intro.texture_size[1] + 20
            spaces += 1
        # submit and cancel buttons
        spaces += 2
        parts += 2
        # larger, smaller buttons for calibration
        spaces += 1
        parts += 1
        # calibration
        spaces += 1
        parts += self._carousel_parts
        # separator
        spaces += 1
        parts += 1
        # other buttons
        if self._messageview_back_handler is not None:
            # back button
            spaces += 1
            parts += 1
        # calc height
        height += parts * self._part_height
        height += spaces * self._spacing
        height += 2 * self._padding
        # build the popup
        self._messageview_popup = Popup(title=title,
                                        size_hint=(None, None),
                                        size=(dp(self._popup_width), dp(height)),
                                        auto_dismiss=False)
        # layout
        layout = BoxLayout(orientation='vertical',
                           padding=(dp(self._padding), dp(self._padding)),
                           spacing=dp(self._spacing))
        # sizes
        unit_size = (dp(self._part_width), dp(self._part_height))
        half_unit_size = (dp(self._part_width/2), dp(self._part_height))

        # add the parts to the layout
        if using_help:
            layout.add_widget(intro)
        # add the smaller and larger buttons and calibrated square
        self._calibration_input = _CalibratedSquare(value,
                                                    size=self._carousel_size,
                                                    size_hint=(None, None))
        smaller_button = Button(text='Smaller',
                               size=half_unit_size)
        smaller_button.bind(on_press=self._calibration_input.handle_on_smaller_pressed)
        smaller_larger_layout = BoxLayout(orientation='horizontal',
                                          size=unit_size)
        larger_button = Button(text='Larger',
                               size=half_unit_size)
        larger_button.bind(on_press=self._calibration_input.handle_on_larger_pressed)
        smaller_larger_layout.add_widget(smaller_button)
        smaller_larger_layout.add_widget(larger_button)
        layout.add_widget(smaller_larger_layout)
        layout.add_widget(self._calibration_input)
        self._messageview_popup.bind(on_open=self._calibration_input.handle_on_open)
        # add the separator
        separator = self.new_separator()
        layout.add_widget(separator)
        # add the submit button
        layout.add_widget(Button(text='Submit',
                                 on_press=self.w_calibrations_submit_handler,
                                 size=unit_size))
        if self._messageview_back_handler is not None:
            layout.add_widget(Button(text='Back',
                                     on_press=self.w_messageview_back_handler,
                                     size=unit_size))
        layout.add_widget(Button(text='Cancel',
                                 on_press=self.w_calibration_popup_dismiss,
                                 size=unit_size))
        self._messageview_popup.content = layout
        self._messageview_popup.open()

    def w_calibrations_submit_handler(self, instance):
        value = self._calibration_input.value
        self.w_calibration_popup_dismiss()
        self._messageview_command_handler(value, *self._messageview_command_handler_largs)

    def w_calibration_popup_dismiss(self, *not_used):
        self._messageview_popup.dismiss()
        self._calibration_input = None
        self._messageview_popup = None
